/*
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 *
 * Copyright @2008 the original author or authors.
 */
package com.bluestone.proxy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.net.Socket;
import java.net.URI;
import java.util.HashMap;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.StatusLine;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.SerializableEntity;
import org.apache.http.impl.DefaultHttpClientConnection;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicStatusLine;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.util.ByteArrayBuffer;
import org.apache.http.util.EntityUtils;

import com.bluestone.util.Util;

/**
 * @author <a href="mailto:bluesotne.master@gmail.com">daniel.q</a>
 *
 */
public class HttpProxyServer implements HttpRequestHandler{
	public static void main(String args[]) {
		HttpProxyServer proxy = new HttpProxyServer();
		proxy.start(808);
	}
	
	private static HttpProxyServer proxyserver = null;
	
	private HttpServer server;
	private byte [] bytes = null;
	private byte [] decodebytes = null;
	private Header [] headers = null;
	private StatusLine statusline = null;
	private Vector dataList = new Vector();
	private DataFilter filter = null;
	private static boolean interceptFlag = false;
	private String jsfile = null;
	
	private String js = null;
	
	private String jsid = null;
	
	private HashMap jsHash = new HashMap();
	
	private CountDownLatch requestCount = null;
	
    public static HttpProxyServer getHttpProxyServer(){
    	if(proxyserver==null){
    		proxyserver = new HttpProxyServer();
    	}
    	return proxyserver;
    }
	public boolean executeJS(String jsid,String js){
		
		this.jsid = jsid;
		this.js = js;
		
		requestCount = new CountDownLatch(1);
		try {
			requestCount.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
			return false;
		}
		
		return true;
	}		
	
 
	public void shutdown(){
		server.shutdown();
	}
	
	public void start(int port){
		try {
			server = new HttpServer(port);
			server.registerHandler("*", this);	        
	        server.start();
	        
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
    public  synchronized void handle(
            final HttpRequest request, 
            final HttpResponse response, 
            final HttpContext context) throws HttpException, IOException {
                        
        //handle bluestone js file
    	String url = request.getRequestLine().getUri();
   	    Util.warn("request url:"+url);
   	    
   	    String relativePath = url;
   	    if(url.startsWith("http")){
   	    	relativePath = url.substring(url.indexOf("/", 8));
   	    }
   	    
    	if(relativePath.startsWith("/bluestone/")){
    		relativePath = relativePath.substring(11).trim();
    		if("ajax.do".equalsIgnoreCase(relativePath)){
	    		if(HttpEntityEnclosingRequest.class.isInstance(request)){
	    			HttpEntityEnclosingRequest enclosingRequest = (HttpEntityEnclosingRequest)request;
	    			String command = null;
	    			String id = null;
	    			String paramsStr = EntityUtils.toString(enclosingRequest.getEntity());
	    			String [] params = paramsStr.split("&");
	    			for(int i=0; i< params.length; i++){
	    				if(params[i].startsWith("command=")){
	    					command = params[i].substring(8);
	    				}
	    				if(params[i].startsWith("jsid=")){
	    					id = params[i].substring(5);
	    				}	    				
	    			}
	    				    			
    				StringBuffer buffer = new StringBuffer();
	    			if("query".equalsIgnoreCase(command)){
	    				if(js == null){
	    					buffer.append(AJAXUtil.CALL_NOTHING);	    					
	    				}else{
	    					buffer.append(AJAXUtil.CALL_FUNCTION);
		    				buffer.append(jsid);
	    					buffer.append(js);
	    					jsHash.put(jsid,js);		    				
	    				}
	    				js = null;
	    				jsid = null;	    				
	    			}else if("success".equalsIgnoreCase(command)){
	    				buffer.append(AJAXUtil.CALL_SUCCESS);
	    				buffer.append(id);
	    				if(jsHash.containsKey(id)){
	    					jsHash.remove(id);
	    					if(requestCount!=null){
	    						requestCount.countDown();
	    					}
	    				}
	    			}

	    			bytes = buffer.toString().getBytes();
	    			headers = new Header[1];
	    			headers[0] = new BasicHeader(HTTP.CONTENT_TYPE,"text/xml");
	    			statusline = new  BasicStatusLine(HttpVersion.HTTP_1_1,HttpStatus.SC_OK,null);
    			}
    		}else if("intercept.do".equalsIgnoreCase(relativePath)){
    	    	//handle intercept action 
    	    	if(BasicHttpEntityEnclosingRequest.class.isInstance(request)){
    	    		int statuscode = HttpStatus.SC_OK;
    	    		BasicHttpEntity entity = (BasicHttpEntity)((BasicHttpEntityEnclosingRequest)request).getEntity();
    	    		bytes = EntityUtils.toByteArray(entity);    		
    	    		ByteArrayInputStream in = new ByteArrayInputStream(bytes);    		
    	    		ObjectInputStream objin = new ObjectInputStream(in);    		
    	    		try {
    					Object obj = objin.readObject();
    					if(InterceptCommand.class.isInstance(obj)){
    						InterceptCommand command = (InterceptCommand)obj;
    						handleCommand(command);
    					}
    				} catch (ClassNotFoundException e) {
    					statuscode = HttpStatus.SC_BAD_REQUEST;
    					e.printStackTrace();
    				}
    							
    		    	SerializableEntity outputentity  = new SerializableEntity(dataList,true);        
    		        response.setEntity(outputentity);  
    		        response.setStatusCode(statuscode);		
    		        return;
    	    	}    			
    		}else{    		
	    		//handle the file request
	    		File file = new File(relativePath);
	    		if(file.exists()){
	        		java.io.FileInputStream in = new java.io.FileInputStream(file);
	        		byte [] temps = new byte[1024];
	        		int count = 0;
	    			ByteArrayBuffer buf = new ByteArrayBuffer((1024 * 4));
	        		while((count=in.read(temps))>0){
	        			buf.append(temps,0,count);
	        		} 
	        		in.close();
	        		bytes = buf.toByteArray();
	    			headers = new Header[1];
	    			headers[0] = new BasicHeader(HTTP.CONTENT_TYPE,"text/html");
	    			statusline = new  BasicStatusLine(HttpVersion.HTTP_1_1,HttpStatus.SC_OK,null);   			
	    		}
    		}    		
    	}else{    	
    		if(!getResponse(request,context)){
				if(requestCount!=null){
					requestCount.countDown();
				}
    			return;
    		}
    	}
        
        response.setHeaders(headers);	

        if(statusline.getStatusCode() == HttpStatus.SC_OK){  
	       // decode if content type is start with  text/* 
	        String content = this.decodecontent(response);
	       // Util.info("decode:"+bytes.length);
	        if(content!=null){
	        	this.decodebytes = content.getBytes();
	        }else{
	        	this.decodebytes = bytes;
	        }
	        

	       //add js file if content type equals text/html
	       content = insertjsfile(response, content);

	        
	        //System.out.println("insert js content:"+content);
	        
	        //encode if content type is start with text/*
	        if(content!=null){
	        	this.encodeContent(response, content);
	        }
	        
	        //Util.info("encode:"+bytes.length);
        }
        //System.out.println("encode content:"+new String(bytes));
        
        if(bytes==null){
        	bytes = new String("this is a test").getBytes();
        }
        ByteArrayEntity entity = new ByteArrayEntity(bytes);
        response.setEntity(entity);
        response.setStatusLine(statusline);
               
        if(HttpProxyServer.interceptFlag){
        	if(checkFilter(response)){
	        	InterceptData bean = new InterceptData();
	        	bean.setContent(this.decodebytes);
	        	HashMap headerMap = new HashMap();
	        	Header [] temp =  response.getAllHeaders();
	        	for(int i=0; i< temp.length; i++){
	        		headerMap.put(temp[i].getName(), temp[i].getValue());
	        	}
	        	bean.setHeaders(headerMap);        	
	        	bean.setStatusCode(response.getStatusLine().getStatusCode());
	        	bean.setUri(request.getRequestLine().getUri());
	        	dataList.add(bean);
	        	
	        	System.out.println("intercept data:"+request.getRequestLine().getUri());
        	}
        }
        //System.out.println(response.getHeaders(HTTP.CONTENT_TYPE)[0].getValue());
        
    }

    private void handleCommand(InterceptCommand command){    	
    	String cmd = command.getCommand();
    	if(InterceptCommand.STARTINTERCEPT.equalsIgnoreCase(cmd)){
    		System.out.println("start intercept data");
    		dataList.clear();
    		filter = command.getFilter();
    		interceptFlag = true;
    		
    	}else if(InterceptCommand.STOPINTERCEPT.equalsIgnoreCase(cmd)){
    		System.out.println("stop intercept data");
    		interceptFlag = false;	
    		filter = null;
    	}
    }
    
    private String decodecontent(HttpResponse response){
		String content = null;
    	try{    		
			Header [] headers = response.getHeaders(HTTP.CONTENT_TYPE);
			if(headers!=null && headers.length>0){
				//Util.warn("response content type:"+headers[0].getValue());
				if(!headers[0].getValue().startsWith("text/")){
					return null;
				}
			}
			
			//decode content
			String encoding = null;
			headers = response.getHeaders(HTTP.CONTENT_ENCODING);
			if(headers!=null && headers.length>0){
				encoding = headers[0].getValue();
			}
			//Util.info("response content encoding:"+encoding);
			
			if(encoding!=null && "gzip".equalsIgnoreCase(encoding)){
				ByteArrayInputStream bytein = new ByteArrayInputStream(bytes);
				GZIPInputStream gzipin = new GZIPInputStream(bytein);
				ByteArrayBuffer buffer = new ByteArrayBuffer((1024 * 8));
		        try {
		            byte[] tmp = new byte[1024 * 4];
		            int l;
		            while((l = gzipin.read(tmp)) != -1) {
		                buffer.append(tmp, 0, l);
		            }
		        } finally {		        	
		        	gzipin.close();
		        }			        
		        bytes = buffer.toByteArray();	
		        //Util.info("unzip:"+bytes.length);
			}
			
			//handle charset
			String charset = null;
			headers = response.getHeaders(HTTP.CONTENT_TYPE);
			if(headers!=null && headers.length>0){
				String type = headers[0].getValue();
				String [] elements = type.split(";");
				if(elements!=null && elements.length >0){
					for(int i=0; i< elements.length; i++){
						String temp = elements[i].trim();
						if(temp.startsWith("charset")){
							charset = temp.split("=")[1];
						}						
					}
				}
			}
			//Util.info("response charset:"+charset);
			if(charset==null){
				content = new String(bytes);
			}else{
				content = new String(bytes,charset);
			}  
			//Util.info("local charset:"+bytes.length);
    	}catch(Exception me){
    		me.printStackTrace();
    	}
    	    	
    	return content;
    }
    
    private void encodeContent(HttpResponse response,String content){
    	try{
    		
			String encoding = null;
			headers = response.getHeaders(HTTP.CONTENT_ENCODING);
			if(headers!=null && headers.length>0){
				encoding = headers[0].getValue();
			}
			
			//handle charset
			String charset = null;
			headers = response.getHeaders(HTTP.CONTENT_TYPE);
			if(headers!=null && headers.length>0){
				String type = headers[0].getValue();
				String [] elements = type.split(";");
				if(elements!=null && elements.length >0){
					for(int i=0; i< elements.length; i++){
						String temp = elements[i].trim();
						if(temp.startsWith("charset")){
							charset = temp.split("=")[1];
						}						
					}
				}
			}
			
			//change charset
			if(charset!=null){
				bytes = content.getBytes(charset);
			}else{
				bytes = content.getBytes();
			}
			//Util.info("response charset:"+bytes.length);
			
			//encode the content
			if(encoding!=null && "gzip".equalsIgnoreCase(encoding)){
				ByteArrayOutputStream byteout = new ByteArrayOutputStream();
				GZIPOutputStream gzipout = new GZIPOutputStream(byteout);	
				gzipout.write(bytes);
				gzipout.finish();
				gzipout.flush();
				bytes = byteout.toByteArray();
			} 	
			//Util.info("gzip:"+bytes.length);
    	}catch(Exception me){
    		me.printStackTrace();
    	}
    }
    
    private String insertjsfile(HttpResponse response,String content){
    	String result = content;
        try {
        	
        	Header [] headers = null;
        	
        	if(jsfile==null){
        		return result;
        	}
    		String filepath = jsfile;    		
    		String scripts = "<script language=\"JavaScript\" src=\""+filepath+"\"></script>";        	
        	
        
        	//add js file to text/html
			headers = response.getHeaders(HTTP.CONTENT_TYPE);
			if(headers!=null && headers.length>0){
				if(!headers[0].getValue().startsWith("text/html")){
					return result;
				}
			}
						
			//add js file
			int pos = content.indexOf("<head>");
			System.out.println(pos);
			if(pos == -1){
				pos = content.indexOf("<HEAD>");
			}
			if(pos!=-1){
				result = content.substring(0,pos+6) + scripts + content.substring(pos+6); 
			}
												
		} catch (Exception e) {
			e.printStackTrace();
		}   
		return result;
    }
    
 
    private boolean checkFilter(HttpResponse response){
    	boolean flag = false;

    	if(this.filter == null){
    		return true;
    	}
    	//check content type
    	String filtertype = filter.getContentType();
    	    	
    	if(!filtertype.equals("*/*")){    		
	    	Header [] types = response.getHeaders(HTTP.CONTENT_TYPE);
	    	if(types!=null){
	    		for(int i=0; i< types.length; i++){
	    			String type = types[i].getValue();
	    			String [] values = filtertype.split(";");
	    			for(int j=0; j < values.length; j++){
	    				if(type.indexOf(values[j])>=0){
	    					flag = true;
	    					break;
	    				}
	    			}
	    			if(flag){
	    				break;
	    			}
	    		}
	    	}
    	}    	
    	return flag;
    }
    
	private  boolean getResponse(HttpRequest request,HttpContext context){
		boolean flag = true;
		HttpResponse response = null;
		DefaultHttpClientConnection conn = null;
		Socket socket = null;
		try {
			HttpClient client = new HttpClient(context);
			conn = new DefaultHttpClientConnection();
			String h = new URI(request.getRequestLine().getUri()).getHost();
			int p = new URI(request.getRequestLine().getUri()).getPort();
			if(p == -1){
				p = 80;
			}
			HttpHost host = new HttpHost(h, p);
			if (!conn.isOpen()) {
				socket = new Socket(host.getHostName(), host.getPort());
				conn.bind(socket, client.getParams());
			}
								
	        request.removeHeaders("If-Modified-Since");
	        request.removeHeaders("If-None-Match");
	        request.removeHeaders(HTTP.CONTENT_LEN);
	        request.removeHeaders("Proxy-Connection");	 
            
	        
            String target = request.getRequestLine().getUri();
            if(target.startsWith("http") ){
            	int pos = target.indexOf("/", 8);
            	target = target.substring(pos);
            }

            BasicHttpEntityEnclosingRequest newrequest = new BasicHttpEntityEnclosingRequest(request.getRequestLine().getMethod(),target );
            newrequest.setHeaders(request.getAllHeaders());
            if(HttpEntityEnclosingRequest.class.isInstance(request)){
            	newrequest.setEntity(((HttpEntityEnclosingRequest)request).getEntity());            	
            }
            
            response = client.execute(newrequest, host, conn);
			            
            HttpEntity entity = response.getEntity();
            bytes = EntityUtils.toByteArray(entity);
            decodebytes = bytes;
                                    
            response.removeHeaders(HTTP.CONTENT_LEN);
            response.removeHeaders(HTTP.TRANSFER_ENCODING);

            headers = response.getAllHeaders();
            
            statusline = response.getStatusLine();
            
		} catch (Exception me) {
			flag = false;
			me.printStackTrace();
		} finally {
			if(conn!=null){
            try {
            	conn.close();
				System.out.println("close client connect!");
			} catch (IOException e) {
				e.printStackTrace();
			}
			}
        }
		System.out.println("response:"+statusline.getStatusCode());
		return flag;
	}

	public String getJsfile() {
		return jsfile;
	}

	public void setJsfile(String jsfile) {
		this.jsfile = jsfile;
	}
			
}
